#ifndef MEASURE_SIZE_TRACKER_HIT_TEST_VISITOR_H_
#define MEASURE_SIZE_TRACKER_HIT_TEST_VISITOR_H_

#if defined(_MSC_VER)
#pragma once
#endif

#include "geometry_elements.h"
#include "boost\variant\static_visitor.hpp"

class hit_test_visitor : public boost::static_visitor<int>
{
public:

  hit_test_visitor(const point_2 &point, int tolerance, bool selected)
    : tolerance_(tolerance)
    , point_(point)
    , selected_(selected)
  {
  }

  ~hit_test_visitor(void)
  {
  }

public:

  int operator()(const point_2& point) const
  {
    if (point.adjacent(point_, tolerance_)) {
      return point_2::kHitMiddle;
    } else {
      return point_2::kHitNothing;
    }
  }

  int operator()(const segment_2& segment) const
  {
    if (segment.start.adjacent(point_, tolerance_)) {
      return segment_2::kHitStart;
    }

    if (segment.end.adjacent(point_, tolerance_)) {
      return segment_2::kHitEnd;
    }

    if (segment.adjacent(point_, tolerance_)) {
      return segment_2::kHitOn;
    }

    return segment_2::kHitNothing;
  }

  int operator()(const box_2& box) const
  {
    if (box.bottom_right.adjacent(point_, tolerance_)) {
      return box_2::kHitSouthEast;
    }

    if (box.top_right().adjacent(point_, tolerance_)) {
      return box_2::kHitNorthEast;
    }

    if (box.top_left.adjacent(point_, tolerance_)) {
      return box_2::kHitNorthWest;
    }

    if (box.bottom_left().adjacent(point_, tolerance_)) {
      return box_2::kHitSouthWest;
    }

    point_2 topRight = box.top_right();
    segment_2_ref rightSide(box.bottom_right, topRight);
    if (rightSide.adjacent(point_, tolerance_)) {
      return box_2::kHitEast;
    }

    segment_2_ref topSide(box.top_left, topRight);
    if (topSide.adjacent(point_, tolerance_)) {
      return box_2::kHitNorth;
    }

    point_2 bottomLeft = box.bottom_left();
    segment_2_ref leftSide(box.top_left, bottomLeft);
    if (leftSide.adjacent(point_, tolerance_)) {
      return box_2::kHitWest;
    }

    segment_2_ref bottomSide(box.bottom_right, bottomLeft);
    if (bottomSide.adjacent(point_, tolerance_)) {
      return box_2::kHitSouth;
    }

    if (box.within(point_)) {
      return box_2::kHitOn;
    }

    return box_2::kHitNothing;
  }

  int operator()(const angle_box_2& angle_box) const
  {
    point_2 center(angle_box.center_x, angle_box.center_y);
    point_2 topLeft     = angle_box.top_left();
    point_2 topRight    = angle_box.top_right();
    point_2 bottomLeft  = angle_box.bottom_left();
    point_2 bottomRight = angle_box.bottom_right();
    point_2 left        = (topLeft+bottomLeft)/2;
    point_2 right       = (topRight+bottomRight)/2;
    point_2 top         = (topLeft+topRight)/2;
    point_2 bottom      = (bottomLeft+bottomRight)/2;

    double hl           = draw_obj::sHandleSize*2.0;
    double hort         = 0;
    double vert         = 0;

    if (angle_box.half_width>0) {
      hort = (hl+angle_box.half_width)/angle_box.half_width;
    }

    if (angle_box.half_height>0) {
      vert = (hl+angle_box.half_height)/angle_box.half_height;
    }

    point_2 aright  = center.interpolate(right, hort);
    point_2 atop    = center.interpolate(top, vert);
    point_2 aleft   = center.interpolate(left, hort);
    point_2 abottom = center.interpolate(bottom, vert);

    if (bottomRight.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitSouthEast;
    }

    if (topRight.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitNorthEast;
    }

    if (topLeft.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitNorthWest;
    }

    if (bottomLeft.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitSouthWest;
    }

    segment_2_ref rightSide(bottomRight, topRight);
    if (rightSide.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitEast;
    }

    segment_2_ref topSide(topLeft, topRight);
    if (topSide.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitNorth;
    }

    segment_2_ref leftSide(topLeft, bottomLeft);
    if (leftSide.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitWest;
    }

    segment_2_ref bottomSide(bottomRight, bottomLeft);
    if (bottomSide.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitSouth;
    }

    if (aleft.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitLeft;
    }

    if (aright.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitRight;
    }

    if (atop.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitTop;
    }

    if (abottom.adjacent(point_, tolerance_)) {
      return angle_box_2::kHitBottom;
    }

    if (angle_box.within(point_)) {
      return angle_box_2::kHitOn;
    }

    return kMouseHitNothing;
  }

  int operator()(const ring_2& ring) const
  {
    if (ring.adjacent(point_, tolerance_)) {
      return ring_2::kHitBorder;
    }

    if (ring.within(point_)) {
      return ring_2::kHitOn;
    }

    return kMouseHitNothing;
  }

  int operator()(const arc_2& arc) const
  {
    if (point_.adjacent(arc.start_point(), tolerance_)) {
      return arc_2::kHitStart;
    }

    if (point_.adjacent(arc.end_point(), tolerance_)) {
      return arc_2::kHitEnd;
    }

    if (arc.adjacent(point_, tolerance_)) {
      return arc_2::kHitBorder;
    }

    if (arc.within(point_)) {
      return arc_2::kHitOn;
    }

    return kMouseHitNothing;
  }

  int operator()(const annulus_2& annulus) const
  {
    if (annulus.adjacent_start(point_, tolerance_)) {
      return annulus_2::kHitStartBorder;
    }

    if (annulus.adjacent_end(point_, tolerance_)) {
      return annulus_2::kHitEndBorder;
    }

    if (annulus.within(point_)) {
      return annulus_2::kHitOn;
    }

    return kMouseHitNothing;
  }

  int operator()(const annulus_arc_2& annulus_sector) const
  {
    point_2 ss = annulus_sector.start_start_point();
    point_2 es = annulus_sector.end_start_point();
    segment_2_ref ssr(ss, es);

    if (ss.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitStartStart;
    }

    if (es.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitEndStart;
    }

    if (ssr.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitStartSpoke;
    }


    point_2 se = annulus_sector.start_end_point();
    point_2 ee = annulus_sector.end_end_point();
    segment_2_ref esr(se, ee);

    if (se.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitStartEnd;
    }

    if (ee.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitEndEnd;
    }

    if (esr.adjacent(point_, tolerance_)) {
      return annulus_arc_2::kHitEndSpoke;
    }

    if (annulus_sector.adjacent_start(point_, tolerance_)) {
      return annulus_arc_2::kHitStartBorder;
    }

    if (annulus_sector.adjacent_end(point_, tolerance_)) {
      return annulus_arc_2::kHitEndBorder;
    }

    if (annulus_sector.within(point_)) {
      return annulus_arc_2::kHitOn;
    }

    return kMouseHitNothing;
  }

  int operator()(const polygon_2& polygon) const
  {
    int numPoints = polygon.num_points();
    for (int n=0; n<numPoints; ++n) {
      point_2 sp = polygon.at(n);
      //point_2 ep = polygon.at((n+1)%numPoints);

      if (sp.adjacent(point_, tolerance_)) {
        return n*2;
      }

      //if (ep.adjacent(point_, tolerance_)) {
      //  return n*2;
      //}

      //segment_2_ref se(sp, ep);

      //if (se.adjacent(point_, tolerance_)) {
      //  return n*2+1;
      //}
    }

    if (polygon.within(point_)) {
      return polygon_2::kHitOn;
    }

    return kMouseHitNothing;
  }

private:
  bool            selected_;
  int             tolerance_;
  const point_2&  point_;
};

#endif // MEASURE_SIZE_TRACKER_HIT_TEST_VISITOR_H_